<template>
	<div class="default-main">

		<cus-form id="business-code-form" collapsable icon="fa fa-search" label="搜索">
			<template #title-after>
				<el-button icon="el-icon-Search" size="small" type="primary" @click.stop="handleSearch">
					搜索
				</el-button>
				<el-button icon="el-icon-Refresh" size="small" type="primary" @click.stop="resetSearch">
					重置
				</el-button>
			</template>
			<template #form>
				<el-form ref="searchForm" :model="params" label-width="80">
					<el-row :gutter="20" justify="space-between">
						<el-col :span="8">
							<el-form-item label="编码">
								<el-input v-model="params.codeType_like" clearable placeholder="模糊查询"
								          @keyup.enter="handleSearch"/>
							</el-form-item>
						</el-col>
						<el-col :span="8">
							<el-form-item label="名称">
								<el-input v-model="params.codeTypeName_like" clearable placeholder="模糊查询"
								          @keyup.enter="handleSearch"/>
							</el-form-item>
						</el-col>
						<el-col :span="8">
							<el-form-item label="状态">
								<business-code-radio v-model="params.valid_eq" border clearable code-type="businessCodeValid"
								                     @change="handleSearch"/>
							</el-form-item>
						</el-col>
					</el-row>
				</el-form>
			</template>
		</cus-form>

		<cus-table-title id="business-code-table-title" icon="iconfont icon-dropdown" label="业务编码">
			<template #after>
				<el-button v-auth="'sys-business-code:saveBusinessCode'" :loading="addAndEditButtonLoading" icon="el-icon-Plus"
				           size="small" style="margin-left: 20px"
				           type="primary" @click="toAdd">
					{{ $t('table.add') }}
				</el-button>
			</template>
		</cus-table-title>
		<el-table ref="tableRef" v-loading="tableLoading" :data="tableData" :header-cell-class-name="handleHeaderCellClass"
		          :max-height="tableMaxHeight" border
		          class="cus-table" highlight-current-row
		          @sort-change="elTableSortChange($event, params.orderby, handleSearch)">
			<el-table-column align="center" label="序号" width="60">
				<template v-slot="scope">
					{{ initTableIndex(pageRef, scope.$index) }}
				</template>
			</el-table-column>
			<el-table-column align="center" label="编码" min-width="150" prop="codeType" show-overflow-tooltip
			                 sortable="custom"></el-table-column>
			<el-table-column align="center" label="名称" min-width="150" prop="codeTypeName"
			                 show-overflow-tooltip></el-table-column>
			<el-table-column align="center" label="启用" prop="valid" sortable="custom" width="120">
				<template v-slot="{row}">
					<el-switch v-model="row.valid" :active-value="1" :before-change="() => beforeValidChange(row)"
					           :disabled="!hasPermission('sys-business-code:updateBusinessCodeValid')" :inactive-value="0"
					           :loading="validSwitchLoading"/>
				</template>
			</el-table-column>
			<el-table-column :formatter="elTableDateTimeFormatter" align="center" label="更新时间" prop="updateTime"
			                 sortable="custom" width="170"></el-table-column>
			<el-table-column align="center" fixed="right" label="操作" width="180">
				<template v-slot="{row}">
					<el-button v-auth="'sys-business-code:saveBusinessCode'" :loading="addAndEditButtonLoading"
					           icon="el-icon-Edit" size="small" type="primary" @click="toUpdate(row)">
						编辑
					</el-button>
					<el-popconfirm :hide-after="0" :title="`删除该业务编码（${row.codeTypeName}）？`" confirm-button-type="danger"
					               width="auto" @confirm="deleteBusinessCode(row)">
						<template #reference>
							<el-button v-auth="'sys-business-code:deleteBusinessCode'" icon="el-icon-Delete" size="small"
							           type="danger">
								删除
							</el-button>
						</template>
					</el-popconfirm>
				</template>
			</el-table-column>
		</el-table>
		<cus-pagination id="business-code-pagination" ref="pageRef" v-model="tableData" v-model:loading="tableLoading"
		                :pageSize="10" :params="params"
		                url="/system/sys-business-code/readOnly/pageBusinessCode"/>

		<!-- 对话框（新增、修改） -->
		<el-dialog v-model="dialogVisible.detailList" :close-on-click-modal="false" :title="dialogTitle" append-to-body center class="cus-dialog" destroy-on-close
		           draggable width="50%">
			<cus-form-title label="基本信息"/>
			<el-form ref="editFormRef" :model="saveRequest" :rules="editFormRules" label-width="auto">
				<el-row :gutter="20" justify="space-between">
					<el-col :span="8">
						<el-form-item label="业务编码" prop="codeType">
							<el-input v-model="saveRequest.codeType" clearable placeholder="请输入业务编码"/>
						</el-form-item>
					</el-col>
					<el-col :span="8">
						<el-form-item label="显示名称" prop="codeTypeName">
							<el-input v-model="saveRequest.codeTypeName" clearable placeholder="请输入显示名称"/>
						</el-form-item>
					</el-col>
					<el-col :span="8">
						<el-form-item label="启用" prop="valid">
							<el-switch v-model="saveRequest.valid" :active-value="1" :inactive-value="0"/>
						</el-form-item>
					</el-col>
				</el-row>
			</el-form>
			<cus-table-title icon="iconfont icon-dropdown" label="备选项">
				<template #after>
					<el-button icon="el-icon-Plus" size="small" style="margin-left: 20px" type="primary" @click="addRoot">
						添加根节点
					</el-button>
				</template>
			</cus-table-title>
			<el-tree
					ref="treeRef"
					:data="saveRequest.detailList"
					:default-expand-all="true"
					:expand-on-click-node="false"
					class="cus-tree"
					draggable
					node-key="id"
					style="margin-top: 20px"
					@node-drag-start="handleDragStart"
					@node-drop="handleDrop"
			>
				<template v-slot="{node, data}">
					<div class="custom-tree-node">
						<div>
							{{ data.value + ' : ' + data.label }}
						</div>
						<div>
							<el-link :underline="false" type="primary" @click="toEdit(data)">
								<icon name="el-icon-Edit"></icon>
								<div style="margin-left: 2px">编辑</div>
							</el-link>
							<el-link :disabled="node.isLeaf && data.count > 0" :underline="false" style="margin-left: 20px"
							         type="primary" @click="append(node, data)">
								<icon name="el-icon-Plus"></icon>
								<div style="margin-left: 2px">下级</div>
							</el-link>
							<el-popconfirm :disabled="data.count > 0" :hide-after="0"
							               :title="`删除【${data.value} : ${data.label}】和所有下级节点？`"
							               confirm-button-type="danger"
							               width="auto" @confirm="remove(node, data)">
								<template #reference>
									<el-link :disabled="data.count > 0" :underline="false" style="margin-left: 20px" type="danger">
										<icon name="el-icon-Delete"></icon>
										<div style="margin-left: 2px">删除</div>
									</el-link>
								</template>
							</el-popconfirm>
						</div>
					</div>
				</template>
			</el-tree>
			<template #footer>
				<el-button v-auth="'sys-business-code:saveBusinessCode'" :loading="saveButtonLoading" icon="el-icon-CircleCheck"
				           type="primary" @click="save">
					保存
				</el-button>
				<el-button icon="el-icon-CircleClose" type="warning" @click="dialogVisible.detailList = false">
					取消
				</el-button>
			</template>
		</el-dialog>

		<!-- 对话框（修改业务编码详情） -->
		<el-dialog v-model="dialogVisible.editDetail" :close-on-click-modal="false" :close-on-press-escape="true"
		           :show-close="true" append-to-body center class="cus-dialog" destroy-on-close draggable
		           title="修改业务编码详情" width="40%">
			<el-form ref="editDetailFormRef" :model="businessCodeDetail" :rules="editDetailFormRules" label-width="auto">
				<el-form-item label="取值" prop="value">
					<el-input v-model="businessCodeDetail!.value" clearable placeholder="请输入取值"/>
				</el-form-item>
				<el-form-item label="显示名称" prop="label">
					<el-input v-model="businessCodeDetail!.label" clearable placeholder="请输入显示名称"/>
				</el-form-item>
			</el-form>
			<template #footer>
				<el-button icon="el-icon-CircleCheck" type="primary" @click="doEdit">
					确认
				</el-button>
				<el-button icon="el-icon-CircleClose" type="warning" @click="() => {dialogVisible.editDetail = false}">
					取消
				</el-button>
			</template>
		</el-dialog>

	</div>
</template>

<script lang="ts" name="BusinessCode" setup>
import {nextTick, onActivated, onMounted, reactive, ref} from 'vue'
import {
	closeLoading,
	elTableDateTimeFormatter,
	elTableHeaderCellClassName,
	elTableSortChange,
	findInTreeArray,
	focusFormErrorElement,
	initTableIndex,
	messageError,
	messageSuccess,
	showLoading,
	treeArrayIterator
} from '@/utils/commonUtil'
import {useBusinessCodeStore} from '@/stores/modules/businessCode.store'
import CusPagination from '@/components/cus-pagination.vue'
import CusTableTitle from '@/components/cus-table-title.vue'
import CusFormTitle from '@/components/cus-form-title.vue'
import type {FormInstance, FormRules, TableColumnCtx} from 'element-plus'
import {ElTree} from 'element-plus'
import {deleteBusinessCodeApi, getAllBusinessCodeByIdApi, saveBusinessCodeApi, updateBusinessCodeValidApi} from '@/api/system/businessCode.api'
import {cloneDeep, debounce, type DebouncedFunc, max} from 'lodash-es'
import {hasPermission} from '@/utils/authUtil'
import Icon from '@/components/icon.vue'
import BusinessCodeRadio from '@/components/business-code-radio.vue'
import {calcMaxHeight} from '@/utils/layoutUtil'
import {useEventListener} from '@vueuse/core'
import {onBeforeRouteLeave} from 'vue-router'
import type Node from 'element-plus/es/components/tree/src/model/node'
import BusinessCodeCascader from '@/components/business-code-cascader.vue'
import CusForm from '@/components/cus-form.vue'

const businessCodeStore = useBusinessCodeStore()
const tableRef = ref()
const tableMaxHeight = ref<number>(800)
const saveButtonLoading = ref(false)
const tableLoading = ref(false)
const addAndEditButtonLoading = ref(false)
const validSwitchLoading = ref(false)
const params = reactive({
	codeType_like: undefined,
	codeTypeName_like: undefined,
	valid_eq: undefined,
	orderby: []
})
const tableData = ref([])
const dialogVisible = reactive({
	detailList: false,
	editDetail: false
})
const dialogTitle = ref<string>()
const saveRequest = reactive<SaveBusinessCodeRequest>({
	id: undefined,
	codeType: undefined,
	codeTypeName: undefined,
	valid: 1,
	detailList: []
})
const editFormRules = reactive<FormRules>({
	codeType: [
		{required: true, message: '请输入业务编码', trigger: 'change', type: 'string'}
	],
	codeTypeName: [
		{required: true, message: '请输入显示名称', trigger: 'change', type: 'string'}
	],
	valid: [
		{required: true, message: '请选择是否启用', trigger: 'change', type: 'number'}
	]
})
const editDetailFormRules = reactive<FormRules>({
	value: [
		{required: true, message: '请输入取值', trigger: 'change', type: 'string'}
	],
	label: [
		{required: true, message: '请输入显示名称', trigger: 'change', type: 'string'}
	]
})
const pageRef = ref()
const editFormRef = ref<FormInstance>()
const editDetailFormRef = ref<FormInstance>()
const getTableMaxHeight: DebouncedFunc<() => typeof getTableMaxHeight> = debounce(() => {
	tableMaxHeight.value = calcMaxHeight('#business-code-form', '#business-code-table-title', '#business-code-pagination')
	return getTableMaxHeight
}, 100, {
	leading: true,
	trailing: false,
	maxWait: 100
})
const treeRef = ref()
const draggingNodeParent = ref<Node>()
const handleDragStart = (node: Node, ev: Event) => {
	draggingNodeParent.value = node.parent
}
const handleDrop = (draggingNode: Node, dropNode: Node, dropType: ElTreeDropType, ev: Event) => {
	// 落在前后
	if (dropType === 'before' || dropType === 'after') {
		let brotherNodes = dropNode.parent.childNodes
		for (const brotherNode of brotherNodes) {
			let parentId
			if (treeRef.value!.root === dropNode.parent) {
				parentId = 0
			} else {
				parentId = dropNode.parent.data.id
			}
			brotherNode.data.parentId = parentId
			brotherNode.data.sort = brotherNodes.indexOf(brotherNode) + 1
		}
	}
	// 落在内部
	else {
		let brotherNodes = dropNode.childNodes
		for (const brotherNode of brotherNodes) {
			brotherNode.data.parentId = dropNode.data.id
			brotherNode.data.sort = brotherNodes.indexOf(brotherNode) + 1
		}
	}
	for (const child of draggingNodeParent.value!.childNodes) {
		child.data.sort = draggingNodeParent.value!.childNodes.indexOf(child) + 1
	}
}
const businessCodeDetail = ref<SysBusinessCodeDetailVo>()
const toEdit = (data: SysBusinessCodeDetailVo) => {
	businessCodeDetail.value = cloneDeep(data)
	businessCodeDetail.value!.$treeNodeId = data.$treeNodeId!
	dialogVisible.editDetail = true
}
const doEdit = () => {
	editDetailFormRef.value!.validate(pass => {
		if (pass) {
			dialogVisible.editDetail = false
			const detailVo = findInTreeArray(saveRequest.detailList, detailVo => detailVo.$treeNodeId === businessCodeDetail.value?.$treeNodeId)
			if (!detailVo) {
				throw Error('无法匹配节点')
			}
			detailVo.value = businessCodeDetail.value?.value!
			detailVo.label = businessCodeDetail.value?.label!
		} else {
			focusFormErrorElement()
		}
	})
}
const append = (node: Node, data: SysBusinessCodeDetailVo) => {
	const newChild: SysBusinessCodeDetailVo = {
		id: undefined,
		parentId: data.id!,
		value: 'value',
		label: 'label',
		sort: data.children && data.children.length > 0 ? max(data.children.map(detailVo => detailVo.sort))! + 1 : 1
	}
	if (!data.children) {
		Reflect.set(data, 'children', [])
	}
	data.children!.push(newChild)
	node.expand()
}
const addRoot = () => {
	saveRequest.detailList.push({
		id: undefined,
		parentId: 0,
		value: 'value',
		label: 'label',
		sort: saveRequest.detailList && saveRequest.detailList.length > 0 ? max(saveRequest.detailList.map(detailVo => detailVo.sort))! + 1 : 1
	})
}
const remove = (node: Node, data: SysBusinessCodeDetailVo) => {
	const parent = node.parent
	const children = parent.data.children || parent.data
	const index = children.findIndex((detailVo: SysBusinessCodeDetailVo) => detailVo.$treeNodeId === data.$treeNodeId)
	children.splice(index, 1)
}
const handleHeaderCellClass = (args: { rows: BusinessCodeVo[], column: TableColumnCtx<BusinessCodeVo>, rowIndex: number, columnIndex: number }) => {
	elTableHeaderCellClassName(args, params.orderby)
}
const handleSearch = () => {
	pageRef.value.reload()
}
const resetParams = () => {
	params.codeType_like = undefined
	params.codeTypeName_like = undefined
	params.valid_eq = undefined
	params.orderby = []
	// 清除多列的排序状态
	for (const column of tableRef.value?.store.states.columns.value) {
		column.order = null
	}
}
const resetSearch = () => {
	resetParams()
	handleSearch()
}
const cacheSysBusinessCode = () => {
	businessCodeStore.fetchBusinessCodes().then(() => {
		messageSuccess('业务编码缓存已更新')
	})
}
const beforeValidChange = (row: BusinessCodeVo) => {
	validSwitchLoading.value = true
	return new Promise((resolve, reject) => {
		const newValue = row.valid! === 1 ? 0 : 1
		updateBusinessCodeValidApi({codeType: row.codeType!, valid: newValue}).then(response => {
			const success = response.data
			if (success) {
				cacheSysBusinessCode()
				pageRef.value.buildData()
				messageSuccess(`业务编码【${row.codeTypeName}】${newValue === 0 ? '已禁用' : '已启用'}`)
				return resolve(true)
			} else {
				return reject(Error('更新失败'))
			}
		}).catch(error => {
			return reject(error)
		}).finally(() => {
			validSwitchLoading.value = false
		})
	})
}
const resetSaveRequest = () => {
	saveRequest.id = undefined
	saveRequest.codeType = undefined
	saveRequest.codeTypeName = undefined
	saveRequest.valid = 1
	saveRequest.detailList = []
}
const toAdd = () => {
	resetSaveRequest()
	dialogVisible.detailList = true
	dialogTitle.value = '创建业务编码'
	nextTick(() => {
		editFormRef.value!.clearValidate()
	})
}
const toUpdate = (row: SysBusinessCode) => {
	addAndEditButtonLoading.value = true
	resetSaveRequest()
	saveRequest.id = row.id
	saveRequest.codeType = row.codeType
	saveRequest.codeTypeName = row.codeTypeName
	saveRequest.valid = row.valid
	getAllBusinessCodeByIdApi(row.id!).then(response => {
		saveRequest.detailList = response.data
		dialogTitle.value = `编辑业务编码：${row.codeType}（${row.codeTypeName}）`
		dialogVisible.detailList = true
		nextTick(() => {
			editFormRef.value?.clearValidate()
		})
	}).finally(() => {
		addAndEditButtonLoading.value = false
	})
}
const deleteBusinessCode = (row: BusinessCodeVo) => {
	showLoading('正在删除')
	const request: DeleteBusinessCodeRequest = {
		codeType: row.codeType!
	}
	deleteBusinessCodeApi(request).then(() => {
		cacheSysBusinessCode()
		messageSuccess('删除成功')
		pageRef.value.buildData()
	}).finally(() => {
		closeLoading()
	})
}
const save = () => {
	editFormRef.value?.validate(isValid => {
		if (isValid) {
			if (!saveRequest.detailList || saveRequest.detailList.length === 0) {
				messageError('备选项不能为空')
				return
			}
			// 校验所有叶子结点的value是否重复
			const valueSet = new Set<string>()
			treeArrayIterator(saveRequest.detailList, detailVo => {
				if (detailVo.children && detailVo.children.length > 0) {
					return
				}
				if (valueSet.has(detailVo.value)) {
					const message = `业务编码取值【${detailVo.value}】重复`
					messageError(message)
					throw Error(message)
				} else {
					valueSet.add(detailVo.value)
				}
			})
			saveButtonLoading.value = true
			saveBusinessCodeApi(saveRequest).then(() => {
				cacheSysBusinessCode()
				messageSuccess('保存成功')
				dialogVisible.detailList = false
				saveRequest.id ? pageRef.value.buildData() : pageRef.value.reload()
			}).finally(() => {
				saveButtonLoading.value = false
			})
		} else {
			focusFormErrorElement()
		}
	})
}

onMounted(() => {
	useEventListener(window, 'resize', getTableMaxHeight()!)
})

// 该页面开启缓存且首次或后续进入时执行，刷新页面不执行
onActivated(() => {
	useEventListener(window, 'resize', getTableMaxHeight()!)
})

onBeforeRouteLeave(() => {
	window.removeEventListener('resize', getTableMaxHeight)
})

</script>

<style lang="scss" scoped>
.drag {
	cursor: grab;
}

.custom-tree-node {
	flex: 1;
	display: flex;
	align-items: center;
	justify-content: space-between;
	font-size: 14px;
	padding-right: 8px;
}
</style>
